import sys,pdb

from PyQt4.QtGui import * 
from PyQt4.QtCore import * 

from commands import *
from sprite import *
from spritefile import *
from pixel import *

from colorpicker import *
from selection import *

class SpriteFrameCanvasDropTarget(QWidget):
    def __init__(self, index=None, sprite=None):
        QWidget.__init__(self, None)
        self.setAcceptDrops(True)
        self.index = index
        self.sprite = sprite
        self.setAcceptDrops(True)
        self.resize(5,sprite.height + 2)
        self.setMinimumSize(self.size())
        self.setMaximumSize(self.size())
        self.drag_hover = False

    """Event Handlers"""

    def dropEvent(self, ev):
        self.drag_hover = False
        frame_canvas = ev.source()
        #print "Move", frame_canvas.frame.index, "to", self.index
        frame_canvas.sprite.move_frame(frame_canvas.frame.index, self.index)
        self.update()

    def dragEnterEvent(self, ev):
        if ev.source() != self:
            ev.acceptProposedAction()
            self.drag_hover = True
            self.update()

    def dragLeaveEvent(self, ev):
        self.drag_hover = False
        self.update()

class SpriteFrameCanvasWrapper(QWidget):
    def __init__(self, index=None, sprite=None):
        QWidget.__init__(self, None)
        self.index = index
        self.sprite = sprite
        self.hbox_main = QHBoxLayout(self)
        self.drop_target_left = SpriteFrameCanvasDropTarget(index, sprite)
        self.frame_canvas = SpriteFrameCanvas(index, sprite)
        self.drop_target_right = SpriteFrameCanvasDropTarget(index, sprite)
        self.hbox_main.addWidget(self.drop_target_left)
        self.hbox_main.addWidget(self.frame_canvas)
        self.hbox_main.addWidget(self.drop_target_right)
        self.hbox_main.setContentsMargins(0,0,0,0)
        self.hbox_main.setSpacing(0)
        self.hbox_main.setSizeConstraint(QLayout.SetMinAndMaxSize)

    def set_index(self, index):
        self.index = index
        self.drop_target_left.index = index
        self.drop_target_right.index = index
        self.frame_canvas.set_index(index)

class SpriteFrameCanvas(QWidget):
    def __init__(self, index=None, sprite=None):
        QWidget.__init__(self, None)
        self.start_drag = False
        self.dragging = False

        self.sprite = sprite
        
        self.frame = None
        self.set_index(index)
        
        self.setMinimumSize(self.frame.width + 2, self.frame.height + 2)
        self.setMaximumSize(self.frame.width + 2, self.frame.height + 2)
        self.resize(self.frame.width + 2, self.frame.height + 2)
        self.selected = False

    def set_index(self, index):
        self.index = index

        if self.frame:
            self.disconnect(self.frame, SIGNAL("painted()"), self.onpaint)
            self.disconnect(self.frame, SIGNAL("selected(bool)"), self.set_selected)
            self.disconnect(self.frame, SIGNAL("layerChange()"), self.layer_change)

        self.frame = self.sprite.frames[index]

        self.connect(self.frame, SIGNAL("painted()"), self.onpaint)
        self.connect(self.frame, SIGNAL("selected(bool)"), self.set_selected)
        self.connect(self.frame, SIGNAL("layerChange()"), self.layer_change)
        
    def set_selected(self, selected): 
        if self.selected:
            self.sprite.dirty = True
        self.selected = selected
        self.update()
        
    def layer_change(self):
        self.emit(SIGNAL("painted()"))
        self.onpaint()

    def onpaint(self):
        self.update()

    """Event Handlers"""
        
    def paintEvent(self, ev):       
	    # blit the pixmap
        p = QPainter(self)
        p.fillRect(self.rect(),QBrush(QColor("white")))
        
        if self.selected:
	        pen = QPen(QColor("blue"))
        else:
            pen = QPen(QColor(200,200,200))
        p.setPen(pen)
        p.drawRect(0,0,self.rect().width()-1,self.rect().height()-1)
            
        for layer in self.frame.layers:
	        p.drawPixmap(1,1,layer)

        """
        p.setPen(QPen(QColor("black")))
        p.drawText(QPoint(5,10),str(self.frame.index));
        """

    def mousePressEvent(self, ev):
        self.emit(SIGNAL("changeSelection(int)"), self.index)
        self.start_drag = True

    def mouseMoveEvent(self, ev):
        if self.start_drag:
            self.drag = QDrag(self)
            self.payload = QMimeData()
            self.payload.setText(str(self.frame.index))
            self.drag.setMimeData(self.payload)
            self.drag.start()
            self.dragging = True


    def mouseReleaseEvent(self, ev):
        self.emit(SIGNAL("changeSelection(int)"), self.index)
        self.start_drag = False
        self.dragging = False


class ClipboardSpy(QWidget):
    def __init__(self, sprite = None, color_picker = None, frame_canvases_hbox = None):
        QWidget.__init__(self, None)
        self.set_pixmap(None)
        self.resize(50,50)

    def set_pixmap(self, pixmap):
        self.pixmap = pixmap

    def paintEvent(self, ev):
        p = QPainter(self)
        if self.pixmap: 
            p.drawPixmap(0,0,self.pixmap)
        p.end()    

class SpriteEditor(QWidget):
    def __init__(self, sprite = None, frame_canvases_hbox = None, tiled_view = False, color_gradient_list = None, scrollarea = None):
        QWidget.__init__(self, None)
        self.last_mouse = QPoint(0,0)
        self.cursor_x = 0
        self.cursor_y = 0
        self.tiled_view = tiled_view
        self.command_stack = []
        self.lx = None
        self.ly = None
        self.scrollarea = scrollarea
        self.color_picker = ColorPicker()
        self.color_gradient_list = color_gradient_list
        self.frame_canvases_hbox = frame_canvases_hbox
        self.frame_canvases = []

        # brush
        self.brush_id = Qt.SolidPattern

        #self.clipboard_spy = ClipboardSpy()
        # pens
        self.eraser = QPen(QColor(255,255,255,0))
        self.pencil = QPen(QColor("black"))
        # tools
        self.painting = False
        self.eyedropper = False
        self.paste_hover = False
        self.floodfill = False
        # selection tool
        self.selecting = False
        self.selection = Selection(self)
        self.dragging_selection = False
        self.dragging_start_point = False
        self.dragging_selection_start = None
        self.dragging_selection_end = None
        self.selection_start = None
        self.selection_end = None
        self.clipboard = None
        self.clipboard_action = None

        # input
        #self.setMouseTracking(True)
        #self.grabKeyboard()
       
        # set base zoom level
        self.zoomlevel = 4 

        
        self.set_tiled_view(self.tiled_view)

        # initialize empty sprite
        if sprite == None:
            sprite = Sprite(32, 32)
            sprite.add_empty_frame()
        else:
            self.set_sprite(sprite)

        self.selection = Selection(self)

    def set_brush(self, brush_id):
        self.brush_id = brush_id

    def set_tiled_view(self, tiled_view):
        self.tiled_view = tiled_view

    def toggle_tiled_view(self):
        if self.tiled_view:
            self.tiled_view = False
        else:
            self.tiled_view = True
        self.set_size()
        self.update()

    def clear_selection(self):
        self.selection.clear()
        """
        self.selection_start = None
        self.selection_end = None
        """
        self.set_selection_cursor(None)
        self.update()
    
    def add_frame(self):
        new_index = self.sprite.add_empty_frame()
        self.set_selected_frame(new_index)
        
    def duplicate_frame(self):
        new_index = self.sprite.duplicate_frame(self.selected_frame)
        self.set_selected_frame(new_index)

        # fix indices 
        for frame in self.sprite.frames:
            index = self.sprite.frames.index(frame)
            self.sprite.frames[index].index = index

        return new_index
        
    def delete_frame(self):
        # TODO: Confirm.
        if len(self.sprite.frames) > 1:
            print "Deleting frame #", self.selected_frame
            #self.delete_frame_canvas(self.frames[self.selected_frame])
            self.sprite.delete_frame(self.selected_frame)
            self.selected_frame = None
            self.set_selected_frame(0)
           
            self.fix_indices()

    def set_sprite(self, sprite):
        self.sprite = sprite
        
        self.connect(self.sprite, SIGNAL("frameAdded(QObject)"), self.frame_change)
        self.connect(self.sprite, SIGNAL("frameRemoved(QObject)"), self.frame_change)
            
        self.selected_frame = 0
        self.selected_layer = 0
        
        self.clear_frame_canvases()
        
        for frame in self.sprite.frames:
            self.add_frame_canvas(frame)
        
        # This in turn calls set_size.
        self.set_selected_frame(self.selected_frame)
        
    def set_size(self):
        multiplier = self.get_multiplier()

        s = QSize(self.zoomlevel*self.sprite.width*multiplier, self.zoomlevel*self.sprite.height*multiplier)
        self.setMinimumSize(s)
        self.setMaximumSize(s)
        self.resize(s)
        self.update()

    def clear_frame_canvases(self):
        for fc in self.frame_canvases:
            self.frame_canvases_hbox.removeWidget(fc)
            fc.deleteLater()
        self.frame_canvases = []
        self.frame_canvases_hbox.invalidate()

    def frame_change(self, frame):
        self.clear_frame_canvases()
        self.frame_canvases_hbox.invalidate()
        for frame in self.sprite.frames:
            self.add_frame_canvas(frame)
        self.fix_indices()
        self.frame_canvases_hbox.invalidate()
        
    def delete_frame_canvas(self, frame):
        #print "deleting canvas", frame.index
        fc = self.frame_canvases[frame.index]
        self.frame_canvases.remove(fc)
        self.frame_canvases_hbox.removeWidget(fc)
        self.disconnect(fc.frame_canvas, SIGNAL("changeSelection(int)"), self.change_selected_frame)
        fc.deleteLater()
        self.frame_canvases_hbox.invalidate()
        self.fix_indices()

    def fix_indices(self):
        # fix indices 
        for index in range(len(self.sprite.frames)):
            #print "frames[%d].index = %d" % (index, self.sprite.frames[index].index)
            self.sprite.frames[index].index = index
        
    def add_frame_canvas(self, frame):
        frame_index = self.sprite.frames.index(frame)
        fc = SpriteFrameCanvasWrapper(frame_index, self.sprite)
        self.connect(fc.frame_canvas, SIGNAL("changeSelection(int)"), self.change_selected_frame)
        #print "inserting canvas @", frame_index
        self.frame_canvases.insert(frame_index, fc)
        self.frame_canvases_hbox.insertWidget(frame_index, fc)
        self.fix_indices()
        
    def change_selected_frame(self, frame_index):
        #print "changing selection to", frame_index
        self.set_selected_frame(frame_index)
        self.emit(SIGNAL("painted()"))

    def set_selected_layer(self, selected_layer):
        self.selected_layer = selected_layer
        
    def set_selected_frame(self, frame_index):
        if frame_index == None:
            frame_index = 0
        if self.sprite != None:
            for other_frame in self.sprite.frames:
                if other_frame != self.sprite.frames[frame_index]:
                    other_frame.set_selected(False)
                else:
                    self.selected_frame = frame_index
                    self.get_selected_frame().set_selected(True)
            #self.sprite.frames[self.selected_frame].selected = True
            self.sprite_w = self.sprite.width
            self.sprite_h = self.sprite.height
            self.set_size()
    
    def get_selected_frame(self):
        if self.sprite != None and self.selected_frame != None:
	        if self.selected_frame > len(self.sprite.frames):
	            self.set_selected_frame(0)
	        return self.sprite.frames[self.selected_frame]
        else:
            return None
        
    def get_multiplier(self):    
        if self.tiled_view:
            multiplier = 3
        else:
            multiplier = 1

        return multiplier

    """Flipping"""

    def flip(self, scope, horizontally, vertically):
        if scope == "sprite":
            self.sprite.flip(horizontally, vertically)
        elif scope == "frame":
            self.get_selected_frame().flip(horizontally, vertically)
        elif scope == "layer":
            self.get_selected_frame().flip_layer(index)
        elif scope == "selection":
            self.selection.flip(horizontally, vertically)

        self.sprite.dirty = True
        self.update()

    """ Nudging """

    def nudge(self, offset):
        self.get_selected_frame().nudge(self.selected_layer, offset)
        self.sprite.dirty = True
        self.update()

    """ Gradient """

    def left_color(self):
        self.color_gradient_list.left_color()

    def right_color(self):
        self.color_gradient_list.right_color()

    """ HSV Offset """
    def hsv_offset(self, h, s, v):
        self.new_command_bucket()
        for frame in self.sprite.frames:
            for layer in frame.layers:
                hc = HSVOffsetCommand(layer, h, s, v)
                hc.do()
                self.add_to_current_command_bucket(hc)
        self.sprite.dirty = True
        self.update()

    """ Color Swap """
    def color_swap(self, color_a, color_b):
        layer = self.get_selected_frame().layers[self.selected_layer]
        pc = PaintingCommand(layer)
        self.add_to_current_command_bucket(pc)
        result = self.get_selected_frame().color_swap(self.selected_layer, color_a.color, color_b.color)
        self.sprite.dirty = True
        self.update()
        return result
            

    """ Selection Cursor """

    def set_selection_cursor(self, ev = None):
        # TODO: Update to use new selection code!
        if ev:
            if self.selection_start and self.selection_end:
                rect = self.get_selection_rect()
                if rect.contains(QPoint(self.cursor_x, self.cursor_y)):
                    if not ev.buttons():
                        if QApplication.overrideCursor() != Qt.OpenHandCursor:
                            QApplication.restoreOverrideCursor()
                            QApplication.setOverrideCursor(QCursor(Qt.OpenHandCursor))
                            return
                    else:
                        if QApplication.overrideCursor() != Qt.ClosedHandCursor:
                            QApplication.restoreOverrideCursor()
                            QApplication.setOverrideCursor(QCursor(Qt.ClosedHandCursor))
                            return
        # else
        QApplication.restoreOverrideCursor()

    """Key Statuses"""
            
    def check_floodfill(self, ev):
        if ev.modifiers() & Qt.AltModifier:
            if self.floodfill == False:
	            self.floodfill = True
        else:
            if self.floodfill == True:
	            self.floodfill = False
        
    def check_selecting(self, ev):
        if ev.modifiers() & Qt.ShiftModifier:
            if self.selecting == False:
	            self.selecting = True
        else:
            if self.selecting == True:
	            self.selecting = False

    def check_eyedropper(self, ev):
        if ev.modifiers() & Qt.ControlModifier:
            if self.eyedropper == False:
	            self.eyedropper = True
        else:
            if self.eyedropper == True:
	            self.eyedropper = False

    def check_paste_hover(self, ev):
        if ev.key() == 0x20: # space
            if self.paste_hover:
                self.paste_hover = False
            else:
                self.clear_selection()
                self.paste_hover = True
            self.update()
            return
        self.update()

    """Commands"""

    def do_fill(self, ev):
        # translate mouse position
        tx = ev.pos().x() / self.zoomlevel
        ty = ev.pos().y() / self.zoomlevel

        fc = FillCommand(self.get_selected_frame().layers[self.selected_layer], tx, ty, self.color_picker.selected_color)

        try:
            fc.do()
        except Exception, ex:
            print ex
            fc.undo()

        self.add_to_current_command_bucket(fc)

        self.get_selected_frame().emit(SIGNAL("painted()"))
        self.emit(SIGNAL("painted()"))
        self.sprite.dirty = True
        self.update()
        
    def do_paint(self, ev):
        # translate mouse position
        multiplier = self.get_multiplier()
        tx = ev.pos().x() / self.zoomlevel
        ty = ev.pos().y() / self.zoomlevel

        if self.lx == None:
            self.lx = tx
                
        if self.ly == None:
            self.ly = ty
            
        if self.erasing:
            pen = self.eraser
        else:
            pen = self.color_picker.get_pen()

        layer = self.get_selected_frame().layers[self.selected_layer]

        if not self.erasing:
            tmp_bigbuffer = QPixmap(self.sprite.width * multiplier, self.sprite.height * multiplier)
            tmp_bigbuffer.fill(QColor(0,0,0,0))

            pc = PaintCommand(tmp_bigbuffer, tx, ty, self.lx, self.ly, pen, self.erasing, multiplier, layer)
            try:
                pc.do()
            except Exception, ex:
                print ex
                pc.undo()

            pc.freeze_layer(layer)

            q = QPainter(layer)

            for vert in range(0, multiplier):
                for horiz in range(0, multiplier):
                    rect = QRect(layer.width() * horiz, layer.height() * vert, layer.width(), layer.height())
                    q.drawPixmap(QPoint(0,0), tmp_bigbuffer, rect)

            q.end()

            self.add_to_current_command_bucket(pc)
        else:
            while tx >= self.sprite.width:
                tx -= self.sprite.width
            while ty >= self.sprite.height:
                ty -= self.sprite.height

            pc = PaintCommand(layer, tx, ty, self.lx, self.ly, pen, self.erasing, multiplier)

            try:
                pc.do()
            except Exception, ex:
                print ex
                pc.undo()

            self.add_to_current_command_bucket(pc)

        self.get_selected_frame().emit(SIGNAL("painted()"))
        self.emit(SIGNAL("painted()"))

        self.sprite.dirty = True

        self.update()
            
        self.lx = tx
        self.ly = ty

    """Command Handling"""

    def new_command_bucket(self):
        self.command_stack.append([])
        #print len(self.command_stack)

    def add_to_current_command_bucket(self, command):
        if len(self.command_stack) > 0:
            self.command_stack[len(self.command_stack) - 1].append(command)

    def add_to_command_stack(self, command):
        #self.command_stack = self.command_stack[:self.command_stack_pointer]
        for bucket in self.command_stack:
            for command_ in bucket:
                if command_.undone:
                    self.command_stack.remove(command_)

        self.command_stack.append(command)

    """Undo / Redo"""

    def undo(self):
        # find most recent done action
        command = None
        undo_commands = []
        self.command_stack.reverse()
        for bucket in self.command_stack:
            bucket.reverse()
            for command_ in bucket:
                if not command_.undone:
                    undo_commands.append(command_)
            if len(undo_commands) > 0:
                break
            bucket.reverse()
        self.command_stack.reverse()
        if undo_commands:
            for command in undo_commands:
                command.undo()
        else:
            print "No command found."
            pass
        self.get_selected_frame().emit(SIGNAL("painted()"))
        self.emit(SIGNAL("painted()"))
        self.sprite.dirty = True
        self.update()

    def redo(self):
        # TODO: Update to use buckets.
        command = None

        # find most recent undone action
        self.command_stack.reverse()
        for command_ in self.command_stack:
            if not command_.undone:
                command = command_
                break
        self.command_stack.reverse()

        if command:
            #print "Redoing", command
            command.do()
        else:
            #print "No command found", command
            pass
        self.get_selected_frame().emit(SIGNAL("painted()"))
        self.emit(SIGNAL("painted()"))
        self.update()

    """Zoom"""

    def zoom_in(self):
        self.zoomlevel += 1
        self.set_size()
        self.sprite.dirty = True
        self.update()

    def zoom_out(self):
        if self.zoomlevel != 1:
            self.zoomlevel -= 1
            self.set_size()
            self.sprite.dirty = True
            self.update()

    def set_zoomlevel(self, zoomlevel):
        self.zoomlevel = zoomlevel
        self.sprite.dirty = True
        self.set_size()
    
    """Event Handlers"""

    def paintEvent(self, ev):       
        multiplier = self.get_multiplier()
        if self.sprite.frame_dirty:
            self.frame_change(ev)
            self.sprite.reset_dirty_flag()

        if self.sprite.dirty:
            self.sprite.dirty = False
            # blit the pixmap
            self.last_pixmap = QPixmap(self.size().width(), self.size().height())

            p = QPainter(self.last_pixmap)
            p.fillRect(self.rect(),QBrush(QColor("white")))
            
            sprite_index = self.sprite.frames.index(self.get_selected_frame())
            if (sprite_index - 1) >= 0:
                previous_sprite = self.sprite.frames[sprite_index - 1]
                do_onionskin = True
            else:
                previous_sprite = None
                do_onionskin = False

            for vert in range(0,multiplier):
                for horiz in range(0,multiplier):
                    for layer in self.get_selected_frame().layers:
                        layer_index = self.get_selected_frame().layers.index(layer)
                        tmp = layer.scaled(self.size().width()/multiplier, self.size().height()/multiplier)
                        if do_onionskin:
                            os_layer = self.sprite.frames[sprite_index - 1].layers[layer_index]
                            tmp_os = os_layer.scaled(self.size().width()/multiplier, self.size().height()/multiplier)
                        try:
                            p.setOpacity(1.0)
                            p.drawPixmap(tmp.width()*vert,tmp.width()*horiz,tmp)
                            if do_onionskin:
                                p.setOpacity(0.2)
                                p.drawPixmap(tmp.width()*vert,tmp.width()*horiz,tmp_os)
                        except Exception, ex:
                            print "paintEvent",ex

            del p

        q = QPainter(self)
        q.setOpacity(1.0)
        q.drawPixmap(0,0,self.last_pixmap)

        if self.paste_hover:
            clipboard_pix = self.selection.clipboard.pixmap()

            if not clipboard_pix.isNull():
                #tmptmptmp = clipboard_pix.scaled()

                rect = QRect(self.cursor_x, self.cursor_y, clipboard_pix.width()*self.zoomlevel,clipboard_pix.height()*self.zoomlevel)

                q.drawPixmap(rect, clipboard_pix)

        # Draw Cursor
        # q.drawRect(self.cursor_x, self.cursor_y, self.zoomlevel, self.zoomlevel)
        if self.tiled_view:
            # Draw Grid
            q.setOpacity(0.2)
            w = self.size().width()/multiplier
            q.drawLine(w, 0, w, self.size().height())
            q.drawLine(w*2, 0, w*2, self.size().height())
            q.drawLine(0, w, self.size().width(), w)
            q.drawLine(0,w*2, self.size().width(), w*2)
        del q 

    def mousePressEvent(self, ev):
        self.new_command_bucket()

        self.check_eyedropper(ev)
        self.check_floodfill(ev)
        self.check_selecting(ev)
        
        if self.eyedropper:
            x = ev.pos().x()
            y = ev.pos().y()
            img = self.last_pixmap.toImage()
            pixel = Pixel.getPixel(img, x, y)
            self.color_picker.set_color(pixel.color)
            self.color_gradient_list.try_select_color(pixel.color)
            self.emit(SIGNAL("eyedropper(QColor)"), pixel.color)
        elif self.floodfill:
            self.do_fill(ev)
        elif self.paste_hover:
            self.selection.paste()
        elif self.selecting or self.selection.selection_start or self.selection.selection_end:
            x = ev.pos().x() / self.zoomlevel * self.zoomlevel
            y = ev.pos().y() / self.zoomlevel * self.zoomlevel
            self.selection.mousePress(QPoint(x, y))
        else:
	        self.painting = True
	        
	        if ev.button() == Qt.RightButton:
	            self.erasing = True
	        else:
	            self.erasing = False
	            
	        self.do_paint(ev)
        
    def mouseReleaseEvent(self, ev):
        self.painting = False
        self.selecting = False
        self.dragging_selection = False
        self.erasing = False
        self.lx = None
        self.ly = None

        self.selection.mouseRelease()
    
    def mouseMoveEvent(self, ev):
        x = ((ev.pos().x() / self.zoomlevel)) * self.zoomlevel
        y = ((ev.pos().y() / self.zoomlevel)) * self.zoomlevel
        lx = ((self.last_mouse.x() / self.zoomlevel)) * self.zoomlevel
        ly = ((self.last_mouse.y() / self.zoomlevel)) * self.zoomlevel
        # try desperately to save some CPU cycles by only acting when the zoomed position has moved
        if x != lx or y != ly:
            self.last_mouse = QPoint(ev.pos())

            self.check_eyedropper(ev)
            self.check_floodfill(ev)
            self.check_selecting(ev)
                
            if self.painting:
                self.do_paint(ev)
            elif self.selection.moving_selection:
                self.selection.mouseMove(ev)
            elif self.selecting and ev.buttons():
                x = ((ev.pos().x() / self.zoomlevel) + 1) * self.zoomlevel
                y = ((ev.pos().y() / self.zoomlevel) + 1) * self.zoomlevel
                self.selection.mouseMove(QPoint(x, y))
                #print self.selection_end

            self.cursor_x = round(ev.pos().x() / self.zoomlevel) * self.zoomlevel
            self.cursor_y = round(ev.pos().y() / self.zoomlevel) * self.zoomlevel

            self.set_selection_cursor(ev)

            self.update()

    def keyPressEvent(self, ev):
        self.check_paste_hover(ev)
        self.check_eyedropper(ev)
        self.check_floodfill(ev)
        
    def keyReleaseEvent(self, ev):
        self.check_eyedropper(ev)
        self.check_floodfill(ev)
